1 package org.apache.solr.cloud;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.lang.invoke.MethodHandles;
23 import java.net.URL;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Map;
31 import java.util.concurrent.atomic.AtomicInteger;
32
33 import com.carrotsearch.randomizedtesting.rules.SystemPropertiesRestoreRule;
34 import org.apache.lucene.util.LuceneTestCase;
35 import org.apache.lucene.util.LuceneTestCase.SuppressSysoutChecks;
36 import org.apache.solr.SolrTestCaseJ4;
37 import org.apache.solr.client.solrj.SolrQuery;
38 import org.apache.solr.client.solrj.embedded.JettyConfig;
39 import org.apache.solr.client.solrj.embedded.JettyConfig.Builder;
40 import org.apache.solr.client.solrj.embedded.JettySolrRunner;
41 import org.apache.solr.client.solrj.impl.CloudSolrClient;
42 import org.apache.solr.client.solrj.response.QueryResponse;
43 import org.apache.solr.common.SolrInputDocument;
44 import org.apache.solr.common.cloud.ClusterState;
45 import org.apache.solr.common.cloud.Replica;
46 import org.apache.solr.common.cloud.Slice;
47 import org.apache.solr.common.cloud.SolrZkClient;
48 import org.apache.solr.common.cloud.ZkStateReader;
49 import org.apache.solr.core.CoreDescriptor;
50 import org.apache.solr.util.RevertDefaultThreadHandlerRule;
51 import org.junit.ClassRule;
52 import org.junit.Rule;
53 import org.junit.Test;
54 import org.junit.rules.RuleChain;
55 import org.junit.rules.TestRule;
56 import org.slf4j.Logger;
57 import org.slf4j.LoggerFactory;
58
59
60
61
62
63
64 @SuppressSysoutChecks(bugUrl = "Solr logs to JUL")
65 public class TestMiniSolrCloudCluster extends LuceneTestCase {
66
67 private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
68 protected int NUM_SERVERS = 5;
69 protected int NUM_SHARDS = 2;
70 protected int REPLICATION_FACTOR = 2;
71
72 public TestMiniSolrCloudCluster () {
73 NUM_SERVERS = 5;
74 NUM_SHARDS = 2;
75 REPLICATION_FACTOR = 2;
76 }
77
78 @Rule
79 public TestRule solrTestRules = RuleChain
80 .outerRule(new SystemPropertiesRestoreRule());
81
82 @ClassRule
83 public static TestRule solrClassRules = RuleChain.outerRule(
84 new SystemPropertiesRestoreRule()).around(
85 new RevertDefaultThreadHandlerRule());
86
87 private MiniSolrCloudCluster createMiniSolrCloudCluster() throws Exception {
88 Builder jettyConfig = JettyConfig.builder();
89 jettyConfig.waitForLoadingCoresToFinish(null);
90 return new MiniSolrCloudCluster(NUM_SERVERS, createTempDir(), jettyConfig.build());
91 }
92
93 private void createCollection(MiniSolrCloudCluster miniCluster, String collectionName, String createNodeSet, String asyncId, boolean persistIndex) throws Exception {
94 String configName = "solrCloudCollectionConfig";
95 File configDir = new File(SolrTestCaseJ4.TEST_HOME() + File.separator + "collection1" + File.separator + "conf");
96 miniCluster.uploadConfigDir(configDir, configName);
97
98 Map<String, String> collectionProperties = new HashMap<>();
99 collectionProperties.put(CoreDescriptor.CORE_CONFIG, "solrconfig-tlog.xml");
100 collectionProperties.put("solr.tests.maxBufferedDocs", "100000");
101 collectionProperties.put("solr.tests.ramBufferSizeMB", "100");
102
103 collectionProperties.put("solr.tests.mergePolicy", "org.apache.lucene.index.TieredMergePolicy");
104 collectionProperties.put("solr.tests.mergeScheduler", "org.apache.lucene.index.ConcurrentMergeScheduler");
105 collectionProperties.put("solr.directoryFactory", (persistIndex ? "solr.StandardDirectoryFactory" : "solr.RAMDirectoryFactory"));
106
107 miniCluster.createCollection(collectionName, NUM_SHARDS, REPLICATION_FACTOR, configName, createNodeSet, asyncId, collectionProperties);
108 }
109
110 @Test
111 public void testCollectionCreateSearchDelete() throws Exception {
112
113 final String collectionName = "testcollection";
114 MiniSolrCloudCluster miniCluster = createMiniSolrCloudCluster();
115
116 final CloudSolrClient cloudSolrClient = miniCluster.getSolrClient();
117
118 try {
119 assertNotNull(miniCluster.getZkServer());
120 List<JettySolrRunner> jettys = miniCluster.getJettySolrRunners();
121 assertEquals(NUM_SERVERS, jettys.size());
122 for (JettySolrRunner jetty : jettys) {
123 assertTrue(jetty.isRunning());
124 }
125
126
127 log.info("#### Stopping a server");
128 JettySolrRunner stoppedServer = miniCluster.stopJettySolrRunner(0);
129 assertTrue(stoppedServer.isStopped());
130 assertEquals(NUM_SERVERS - 1, miniCluster.getJettySolrRunners().size());
131
132
133 log.info("#### Starting a server");
134 JettySolrRunner startedServer = miniCluster.startJettySolrRunner();
135 assertTrue(startedServer.isRunning());
136 assertEquals(NUM_SERVERS, miniCluster.getJettySolrRunners().size());
137
138
139 log.info("#### Creating a collection");
140 final String asyncId = (random().nextBoolean() ? null : "asyncId("+collectionName+".create)="+random().nextInt());
141 createCollection(miniCluster, collectionName, null, asyncId, random().nextBoolean());
142 if (asyncId != null) {
143 assertEquals("did not see async createCollection completion", "completed", AbstractFullDistribZkTestBase.getRequestStateAfterCompletion(asyncId, 330, cloudSolrClient));
144 }
145
146 ZkStateReader zkStateReader = miniCluster.getSolrClient().getZkStateReader();
147 AbstractDistribZkTestBase.waitForRecoveriesToFinish(collectionName, zkStateReader, true, true, 330);
148
149
150 log.info("#### updating a querying collection");
151 cloudSolrClient.setDefaultCollection(collectionName);
152 SolrInputDocument doc = new SolrInputDocument();
153 doc.setField("id", "1");
154 cloudSolrClient.add(doc);
155 cloudSolrClient.commit();
156 SolrQuery query = new SolrQuery();
157 query.setQuery("*:*");
158 QueryResponse rsp = cloudSolrClient.query(query);
159 assertEquals(1, rsp.getResults().getNumFound());
160
161
162 zkStateReader.updateClusterState();
163 ClusterState clusterState = zkStateReader.getClusterState();
164 HashMap<String, JettySolrRunner> jettyMap = new HashMap<String, JettySolrRunner>();
165 for (JettySolrRunner jetty : miniCluster.getJettySolrRunners()) {
166 String key = jetty.getBaseUrl().toString().substring((jetty.getBaseUrl().getProtocol() + "://").length());
167 jettyMap.put(key, jetty);
168 }
169 Collection<Slice> slices = clusterState.getSlices(collectionName);
170
171 for (Slice slice : slices) {
172 jettyMap.remove(slice.getLeader().getNodeName().replace("_solr", "/solr"));
173 for (Replica replica : slice.getReplicas()) {
174 jettyMap.remove(replica.getNodeName().replace("_solr", "/solr"));
175 }
176 }
177 assertTrue("Expected to find a node without a replica", jettyMap.size() > 0);
178 log.info("#### Stopping a server");
179 JettySolrRunner jettyToStop = jettyMap.entrySet().iterator().next().getValue();
180 jettys = miniCluster.getJettySolrRunners();
181 for (int i = 0; i < jettys.size(); ++i) {
182 if (jettys.get(i).equals(jettyToStop)) {
183 miniCluster.stopJettySolrRunner(i);
184 assertEquals(NUM_SERVERS - 1, miniCluster.getJettySolrRunners().size());
185 }
186 }
187
188
189 log.info("#### Starting a server");
190 startedServer = miniCluster.startJettySolrRunner(jettyToStop);
191 assertTrue(startedServer.isRunning());
192 assertEquals(NUM_SERVERS, miniCluster.getJettySolrRunners().size());
193
194
195
196 miniCluster.deleteCollection(collectionName);
197 AbstractDistribZkTestBase.waitForCollectionToDisappear(collectionName, zkStateReader, true, true, 330);
198
199
200 String asyncId2 = (random().nextBoolean() ? null : "asyncId("+collectionName+".create)="+random().nextInt());
201 createCollection(miniCluster, collectionName, null, asyncId2, random().nextBoolean());
202 if (asyncId2 != null) {
203 assertEquals("did not see async createCollection completion", "completed", AbstractFullDistribZkTestBase.getRequestStateAfterCompletion(asyncId2, 330, cloudSolrClient));
204 }
205 AbstractDistribZkTestBase.waitForRecoveriesToFinish(collectionName, zkStateReader, true, true, 330);
206
207
208 assertEquals(0, cloudSolrClient.query(new SolrQuery("*:*")).getResults().getNumFound());
209 cloudSolrClient.add(doc);
210 cloudSolrClient.commit();
211 assertEquals(1, cloudSolrClient.query(new SolrQuery("*:*")).getResults().getNumFound());
212
213 }
214 finally {
215 miniCluster.shutdown();
216 }
217 }
218
219 @Test
220 public void testErrorsInStartup() throws Exception {
221
222 final AtomicInteger jettyIndex = new AtomicInteger();
223
224 MiniSolrCloudCluster cluster = null;
225 try {
226 cluster = new MiniSolrCloudCluster(3, createTempDir(), JettyConfig.builder().build()) {
227 @Override
228 public JettySolrRunner startJettySolrRunner(String name, String context, JettyConfig config) throws Exception {
229 if (jettyIndex.incrementAndGet() != 2)
230 return super.startJettySolrRunner(name, context, config);
231 throw new IOException("Fake exception on startup!");
232 }
233 };
234 fail("Expected an exception to be thrown from MiniSolrCloudCluster");
235 }
236 catch (Exception e) {
237 assertEquals("Error starting up MiniSolrCloudCluster", e.getMessage());
238 assertEquals("Expected one suppressed exception", 1, e.getSuppressed().length);
239 assertEquals("Fake exception on startup!", e.getSuppressed()[0].getMessage());
240 }
241 finally {
242 if (cluster != null)
243 cluster.shutdown();
244 }
245 }
246
247 @Test
248 public void testErrorsInShutdown() throws Exception {
249
250 final AtomicInteger jettyIndex = new AtomicInteger();
251
252 MiniSolrCloudCluster cluster = new MiniSolrCloudCluster(3, createTempDir(), JettyConfig.builder().build()) {
253 @Override
254 protected JettySolrRunner stopJettySolrRunner(JettySolrRunner jetty) throws Exception {
255 JettySolrRunner j = super.stopJettySolrRunner(jetty);
256 if (jettyIndex.incrementAndGet() == 2)
257 throw new IOException("Fake IOException on shutdown!");
258 return j;
259 }
260 };
261
262 try {
263 cluster.shutdown();
264 fail("Expected an exception to be thrown on MiniSolrCloudCluster shutdown");
265 }
266 catch (Exception e) {
267 assertEquals("Error shutting down MiniSolrCloudCluster", e.getMessage());
268 assertEquals("Expected one suppressed exception", 1, e.getSuppressed().length);
269 assertEquals("Fake IOException on shutdown!", e.getSuppressed()[0].getMessage());
270 }
271
272 }
273
274 @Test
275 public void testExtraFilters() throws Exception {
276 Builder jettyConfig = JettyConfig.builder();
277 jettyConfig.waitForLoadingCoresToFinish(null);
278 jettyConfig.withFilter(JettySolrRunner.DebugFilter.class, "*");
279 MiniSolrCloudCluster cluster = new MiniSolrCloudCluster(NUM_SERVERS, createTempDir(), jettyConfig.build());
280 cluster.shutdown();
281 }
282
283 @Test
284 public void testCollectionCreateWithoutCoresThenDelete() throws Exception {
285
286 final String collectionName = "testSolrCloudCollectionWithoutCores";
287 final MiniSolrCloudCluster miniCluster = createMiniSolrCloudCluster();
288 final CloudSolrClient cloudSolrClient = miniCluster.getSolrClient();
289
290 try {
291 assertNotNull(miniCluster.getZkServer());
292 assertFalse(miniCluster.getJettySolrRunners().isEmpty());
293
294
295 final String asyncId = (random().nextBoolean() ? null : "asyncId("+collectionName+".create)="+random().nextInt());
296 createCollection(miniCluster, collectionName, OverseerCollectionMessageHandler.CREATE_NODE_SET_EMPTY, asyncId, random().nextBoolean());
297 if (asyncId != null) {
298 assertEquals("did not see async createCollection completion", "completed", AbstractFullDistribZkTestBase.getRequestStateAfterCompletion(asyncId, 330, cloudSolrClient));
299 }
300
301 try (SolrZkClient zkClient = new SolrZkClient
302 (miniCluster.getZkServer().getZkAddress(), AbstractZkTestCase.TIMEOUT, 45000, null);
303 ZkStateReader zkStateReader = new ZkStateReader(zkClient)) {
304
305
306 AbstractDistribZkTestBase.waitForRecoveriesToFinish(collectionName, zkStateReader, true, true, 330);
307
308
309 {
310 int coreCount = 0;
311 for (Map.Entry<String,Slice> entry : zkStateReader.getClusterState().getSlicesMap(collectionName).entrySet()) {
312 coreCount += entry.getValue().getReplicasMap().entrySet().size();
313 }
314 assertEquals(0, coreCount);
315 }
316
317
318 miniCluster.deleteCollection(collectionName);
319 AbstractDistribZkTestBase.waitForCollectionToDisappear(collectionName, zkStateReader, true, true, 330);
320 }
321 }
322 finally {
323 miniCluster.shutdown();
324 }
325 }
326
327 @Test
328 public void testStopAllStartAll() throws Exception {
329
330 final String collectionName = "testStopAllStartAllCollection";
331
332 final MiniSolrCloudCluster miniCluster = createMiniSolrCloudCluster();
333
334 try {
335 assertNotNull(miniCluster.getZkServer());
336 List<JettySolrRunner> jettys = miniCluster.getJettySolrRunners();
337 assertEquals(NUM_SERVERS, jettys.size());
338 for (JettySolrRunner jetty : jettys) {
339 assertTrue(jetty.isRunning());
340 }
341
342 createCollection(miniCluster, collectionName, null, null, true);
343 final CloudSolrClient cloudSolrClient = miniCluster.getSolrClient();
344 cloudSolrClient.setDefaultCollection(collectionName);
345 final SolrQuery query = new SolrQuery("*:*");
346 final SolrInputDocument doc = new SolrInputDocument();
347
348 try (SolrZkClient zkClient = new SolrZkClient
349 (miniCluster.getZkServer().getZkAddress(), AbstractZkTestCase.TIMEOUT, 45000, null);
350 ZkStateReader zkStateReader = new ZkStateReader(zkClient)) {
351 AbstractDistribZkTestBase.waitForRecoveriesToFinish(collectionName, zkStateReader, true, true, 330);
352
353
354 final int numDocs = 1 + random().nextInt(10);
355 for (int ii = 1; ii <= numDocs; ++ii) {
356 doc.setField("id", ""+ii);
357 cloudSolrClient.add(doc);
358 if (ii*2 == numDocs) cloudSolrClient.commit();
359 }
360 cloudSolrClient.commit();
361
362 {
363 final QueryResponse rsp = cloudSolrClient.query(query);
364 assertEquals(numDocs, rsp.getResults().getNumFound());
365 }
366
367
368 zkStateReader.updateClusterState();
369 final ClusterState clusterState = zkStateReader.getClusterState();
370
371 final HashSet<Integer> leaderIndices = new HashSet<Integer>();
372 final HashSet<Integer> followerIndices = new HashSet<Integer>();
373 {
374 final HashMap<String,Boolean> shardLeaderMap = new HashMap<String,Boolean>();
375 for (final Slice slice : clusterState.getSlices(collectionName)) {
376 for (final Replica replica : slice.getReplicas()) {
377 shardLeaderMap.put(replica.getNodeName().replace("_solr", "/solr"), Boolean.FALSE);
378 }
379 shardLeaderMap.put(slice.getLeader().getNodeName().replace("_solr", "/solr"), Boolean.TRUE);
380 }
381 for (int ii = 0; ii < jettys.size(); ++ii) {
382 final URL jettyBaseUrl = jettys.get(ii).getBaseUrl();
383 final String jettyBaseUrlString = jettyBaseUrl.toString().substring((jettyBaseUrl.getProtocol() + "://").length());
384 final Boolean isLeader = shardLeaderMap.get(jettyBaseUrlString);
385 if (Boolean.TRUE.equals(isLeader)) {
386 leaderIndices.add(new Integer(ii));
387 } else if (Boolean.FALSE.equals(isLeader)) {
388 followerIndices.add(new Integer(ii));
389 }
390 }
391 }
392 final List<Integer> leaderIndicesList = new ArrayList<Integer>(leaderIndices);
393 final List<Integer> followerIndicesList = new ArrayList<Integer>(followerIndices);
394
395
396 Collections.shuffle(followerIndicesList, random());
397 for (Integer ii : followerIndicesList) {
398 if (!leaderIndices.contains(ii)) {
399 miniCluster.stopJettySolrRunner(jettys.get(ii.intValue()));
400 }
401 }
402
403
404 Collections.shuffle(leaderIndicesList, random());
405 for (Integer ii : leaderIndicesList) {
406 miniCluster.stopJettySolrRunner(jettys.get(ii.intValue()));
407 }
408
409
410 final List<Integer> restartIndicesList = new ArrayList<Integer>();
411 Collections.shuffle(leaderIndicesList, random());
412 restartIndicesList.addAll(leaderIndicesList);
413 Collections.shuffle(followerIndicesList, random());
414 restartIndicesList.addAll(followerIndicesList);
415 if (random().nextBoolean()) Collections.shuffle(restartIndicesList, random());
416
417
418 for (Integer ii : restartIndicesList) {
419 final JettySolrRunner jetty = jettys.get(ii.intValue());
420 if (!jetty.isRunning()) {
421 miniCluster.startJettySolrRunner(jetty);
422 assertTrue(jetty.isRunning());
423 }
424 }
425 AbstractDistribZkTestBase.waitForRecoveriesToFinish(collectionName, zkStateReader, true, true, 330);
426
427 zkStateReader.updateClusterState();
428
429
430 {
431 final QueryResponse rsp = cloudSolrClient.query(query);
432 assertEquals(numDocs, rsp.getResults().getNumFound());
433 }
434
435
436 miniCluster.deleteCollection(collectionName);
437 AbstractDistribZkTestBase.waitForCollectionToDisappear(collectionName, zkStateReader, true, true, 330);
438 }
439 }
440 finally {
441 miniCluster.shutdown();
442 }
443 }
444
445 }